#include "pika_hal.h"
#include <stdint.h>
#include "PikaVM.h"
#include "driver/spi_master.h"
#include "driver/gpio.h"
#define MP_HW_SPI_MAX_XFER_BYTES (4092)
#define MP_HW_SPI_MAX_XFER_BITS (MP_HW_SPI_MAX_XFER_BYTES * 8) 
// Has to be an even multiple of 8
#define EPD_WIDTH 960
#define EPD_HEIGHT 540

//Rotate mode
#define IT8951_ROTATE_0          0
#define IT8951_ROTATE_90        1
#define IT8951_ROTATE_180       2
#define IT8951_ROTATE_270       3

//Pixel mode (Bit per Pixel)
#define IT8951_2BPP             0
#define IT8951_3BPP             1
#define IT8951_4BPP             2
#define IT8951_8BPP             3

//Endian Type
#define IT8951_LDIMG_L_ENDIAN   0
#define IT8951_LDIMG_B_ENDIAN   1

#define UPDATE_MODE_INIT     0  
//# N/A       2000ms       Display initialization,
#define UPDATE_MODE_DU       1  
//# Low       260ms        Monochrome menu, text input, and touch screen input
#define UPDATE_MODE_GC16     2  
//# * Very Low  450ms        High quality images
#define UPDATE_MODE_GL16     3  
//# * Medium    450ms        Text with white background
#define UPDATE_MODE_GLR16    4  
//#   Low       450ms        Text with white background
#define UPDATE_MODE_GLD16    5  
//#   Low       450ms        Text and graphics with white background
#define UPDATE_MODE_DU4      6  
//# * Medium    120ms        Fast page flipping at reduced contrast
#define UPDATE_MODE_A2       7  
//#   Medium    290ms        Anti-aliased text in menus / touch and screen input
#define UPDATE_MODE_NONE     8

/*-----------------------------------------------------------------------
#IT8951 Command defines
#------------------------------------------------------------------------*/

#//Built in I80 Command Code
#define IT8951_TCON_SYS_RUN         0x0001
#define IT8951_TCON_STANDBY         0x0002
#define IT8951_TCON_SLEEP           0x0003
#define IT8951_TCON_REG_RD          0x0010

//Address of System Registers
//System Registers
#define IT8951_SYS_REG_BASE         0x0000

#define IT8951_I80CPCR   (IT8951_SYS_REG_BASE + 0x04)

//Memory Converter Registers
#define IT8951_MCSR_BASE_ADDR        0x0200
#define IT8951_MCSR                 (IT8951_MCSR_BASE_ADDR + 0x0000)
#define IT8951_LISAR                (IT8951_MCSR_BASE_ADDR + 0x0008)

#define IT8951_TCON_LD_IMG          0x0020
#define IT8951_TCON_LD_IMG_AREA     0x0021
#define IT8951_TCON_LD_IMG_END      0x0022

//I80 User defined command code
#define IT8951_I80_CMD_DPY_AREA      0x0034
#define IT8951_I80_CMD_GET_DEV_INFO  0x0302
#define IT8951_I80_CMD_DPY_BUF_AREA  0x0037
#define IT8951_I80_CMD_VCOM         0x0039

//Register Base Address
#define IT8951_DISPLAY_REG_BASE     0x1000
#define IT8951_LUTAFSR     (IT8951_DISPLAY_REG_BASE + 0x224) 

// for hw_spi config
#define LCD_HOST HSPI_HOST
#define PIN_NUM_MISO 13
#define PIN_NUM_MOSI 12
#define PIN_NUM_CLK  14
#define PIN_NUM_CS   15
#define PIN_NUM_BUSY 27


#define MAIN_PWR_PIN "P2"
#define EXT_PWR_EN_PIN  "P5"
#define EPD_PWR_EN_PIN "P23"
#define KEY_RIGHT_PIN "P39"
#define KEY_PUSH_PIN "P38"
#define KEY_LEFT_PIN "P37"

#define CS_PIN "P15"
#define BUSY_PIN "P27"
#define SCK_PIN "P14"
#define MOSI_PIN "P12"
#define MISO_PIN "P13"

#define IT8951_I80_CMD_GET_DEV_INFO 0x0302

#define LIMIT ((EPD_WIDTH * EPD_HEIGHT) / 8 * 4)

//uint8_t txbuf[LIMIT];

uint8_t* g_epd_buf;
int _endian_type;
int _pix_bpp;

spi_device_handle_t g_t_spi;
/*
typedef struct EPD_dev {

  pika_dev *spi;
  pika_dev *pin_busy;
  pika_dev *pin_cs; 
  pika_dev *main_pwr_pin;
  pika_dev *ext_pwr_en_pin;
  pika_dev *epd_pwr_en_pin;
  pika_dev *key_right_pin;
  pika_dev *key_push_pin;
  pika_dev *key_left_pin;
  }  EPD_dev;

EPD_dev g_epd_dev ={0};
*/
static inline void _pika_hal_GPIO_write(pika_dev *dev, uint32_t val) {
  pika_hal_write(dev, &val, sizeof(val));
}

#define MAX_RETRY 100
bool wait_busy(){
  uint32_t val=0;
  int retry=0;
  
  while (val==0) {
    //pika_hal_read(g_epd_dev.pin_busy, &val, sizeof(val));
    val = gpio_get_level(PIN_NUM_BUSY);
    if (val !=0) break;
    pika_sleep_ms(10);
    retry++;
    if (retry >= MAX_RETRY) {
      pika_debug("busy timeout ** val:%d retry:%d", val, retry);
      return false;
    }
  }
  pika_debug("val:%lx wb retry:%d", val, retry);
  return true;
}

//typedef __int64 mp_int_t;
//typedef unsigned __int64 mp_uint_t;

static uint64_t gcd(uint64_t x, uint64_t y) {
    while (x != y) {
        if (x > y) {
            x -= y;
        } else {
            y -= x;
        }
    }
    return x;
}

void frm_fill_rect(uint8_t* buf, int x, int y, int w, int h, int col){
    col &= 0x0f;
    int stride = EPD_WIDTH;
    uint8_t *pixel_pair = &((uint8_t *)buf)[(x + y * stride) >> 1];
    uint8_t col_shifted_left = col << 4;
    uint8_t col_pixel_pair = col_shifted_left | col;
    unsigned int pixel_count_till_next_line = (stride - w) >> 1;
    bool odd_x = (x % 2 == 1);

    while (h--) {
        unsigned int ww = w;

        if (odd_x && ww > 0) {
            *pixel_pair = (*pixel_pair & 0xf0) | col;
            pixel_pair++;
            ww--;
        }

        memset(pixel_pair, col_pixel_pair, ww >> 1);
        pixel_pair += ww >> 1;

        if (ww % 2) {
            *pixel_pair = col_shifted_left | (*pixel_pair & 0x0f);
            if (!odd_x) {
                pixel_pair++;
            }
        }

        pixel_pair += pixel_count_till_next_line;
    }
}

void frm_set_pixel(uint8_t* buf, int x, int y, int col){
  int stride = EPD_WIDTH;
  uint8_t *pixel = &((uint8_t *)buf)[(x + y * stride) >> 1];

    if (x % 2) {
        *pixel = ((uint8_t)col & 0x0f) | (*pixel & 0xf0);
    } else {
        *pixel = ((uint8_t)col << 4) | (*pixel & 0x0f);
    }
}

void cs_level(uint32_t level){
  pika_debug("cs:%ld\n", level);
  //_pika_hal_GPIO_write(g_epd_dev.pin_cs, level);
  gpio_set_level(PIN_NUM_CS, level);
}

static void hw_spi_transfer(spi_device_handle_t spi, size_t len, const uint8_t *src, uint8_t *dest){
    pika_debug("hw_spi_transfer l:%u", len);
        // Round to nearest whole set of bits
    int bits=8;
    int bits_to_send = len * 8 / bits * bits;

    if (len <= 4) {
        spi_transaction_t transaction = { 0 };

        if (src != NULL) {
            memcpy(&transaction.tx_data, src, len);
            pika_debug("txdata: %02x %02x", transaction.tx_data[0], transacti\
on.tx_data[1]);
        }

        transaction.flags = SPI_TRANS_USE_TXDATA | SPI_TRANS_USE_RXDATA;
        transaction.length = len * 8;
        pika_debug("trans f:%lx l:%d", transaction.flags, transaction.length)\
;
        spi_device_transmit(spi, &transaction);

        if (dest != NULL) {
            memcpy(dest, &transaction.rx_data, len);
            pika_debug("dest %02x %02x", *dest, *(dest+1));

        }
    } else {
        pika_debug("test ----");
        int offset = 0;
        int bits_remaining = bits_to_send;
        int optimum_word_size = 8 * bits / gcd(8, bits);
        int max_transaction_bits = MP_HW_SPI_MAX_XFER_BITS / optimum_word_size * optimum_word_size;
        spi_transaction_t *transaction, *result, transactions[2];
        int i = 0;

        spi_device_acquire_bus(spi, portMAX_DELAY);
        while (bits_remaining) {
            transaction = transactions + i++ % 2;
            memset(transaction, 0, sizeof(spi_transaction_t));

            transaction->length =
                bits_remaining > max_transaction_bits ? max_transaction_bits : \
bits_remaining;

            if (src != NULL) {
                transaction->tx_buffer = src + offset;
            }
            if (dest != NULL) {
                transaction->rx_buffer = dest + offset;
            }

            pika_debug("trans f:%lx l:%d r:%d", transaction->flags, transacti\
on->length, bits_remaining);

            spi_device_queue_trans(spi, transaction, portMAX_DELAY);
            bits_remaining -= transaction->length;

            if (offset > 0) {
                // wait for previously queued transaction
                //MP_THREAD_GIL_EXIT();
                spi_device_get_trans_result(spi, &result, portMAX_DELAY);
                //MP_THREAD_GIL_ENTER();
            }

            // doesn't need ceil(); loop ends when bits_remaining is 0
            offset += transaction->length / 8;
        }
        // wait for last transaction
        //MP_THREAD_GIL_EXIT();
        spi_device_get_trans_result(spi, &result, portMAX_DELAY);
        //MP_THREAD_GIL_ENTER();
        spi_device_release_bus(spi);

    }

}

bool write_command(spi_device_handle_t spi, uint16_t icmd){
  uint8_t cmd[2];
  
  wait_busy();
  cs_level(0);
  cmd[0]=0x60;
  cmd[1]=0x00;
  hw_spi_transfer(spi, 2, cmd, NULL);

  wait_busy();
  cmd[0]=icmd >> 8;
  cmd[1]=icmd & 0xff;
  hw_spi_transfer(spi, 2, cmd, NULL);
  cs_level(1);
  return true;
}

bool write_word(spi_device_handle_t spi, uint16_t data) {
  uint8_t cmd[2];
  
  wait_busy();
  cs_level(0);
  cmd[0]=0x00;
  cmd[1]=0x00;
  hw_spi_transfer(spi, 2, cmd, NULL);

  wait_busy();
  cmd[0]=data >> 8;
  cmd[1]=data & 0xff;
  hw_spi_transfer(spi, 2, cmd, NULL);
  cs_level(1);
  return true;
}

bool write_reg(spi_device_handle_t spi, uint16_t addr, uint16_t data) {
  uint8_t cmd[2];

  write_command(spi, 0x0011);
  write_word(spi, addr);
  write_word(spi, data);
  return true;
}

bool read_word(spi_device_handle_t spi, uint8_t* buf) {
  uint8_t cmd[2];
  
  wait_busy();
  cs_level(0);
  cmd[0]=0x10;
  cmd[1]=0x00;
  hw_spi_transfer(spi, 2, cmd, NULL);

  wait_busy();
  hw_spi_transfer(spi, 2, NULL, buf);
  cs_level(1);
  return true;
}

void lcd_get_dev_info(spi_device_handle_t spi)
{
  esp_err_t ret;
    // When using SPI_TRANS_CS_KEEP_ACTIVE, bus must be locked/acquired
    //spi_device_acquire_bus(spi, portMAX_DELAY);

    //get_dev_info cmd
    //ret=lcd_write_command(spi, IT8951_I80_CMD_GET_DEV_INFO);
    //ESP_ERROR_CHECK(ret);
  uint8_t rxbuf[20];
  uint8_t* rxptr;
  rxptr=rxbuf;
  write_command(spi, IT8951_I80_CMD_GET_DEV_INFO);
  //for (int i=0; i<=10; i++) {
    read_word(spi, rxptr);
    //rxptr+=2;
    //}
    //for (int i=0; i<=20; i++) {
    pika_debug("rxbuf %02x %02x", rxbuf[0], rxbuf[1]);
    //}

}

void epd_set_target_memory_addr(spi_device_handle_t spi, uint32_t tar_addr){
  uint16_t h = (tar_addr >> 16) & 0x0000FFFF;
  uint16_t l = tar_addr & 0x0000FFFF;
  write_reg(spi, IT8951_LISAR +2, h);
  write_reg(spi, IT8951_LISAR, l);

}

#define MAX_RETRY_CHECK_AFSR 3000
bool epd_check_AFSR(spi_device_handle_t spi) {
  int retry=0;
  uint8_t rxbuf[2];

  while (1==1) {
    write_command(spi, IT8951_TCON_REG_RD);
    write_word(spi, IT8951_LUTAFSR);
    read_word(spi, rxbuf);
    if (rxbuf[0]==0 && rxbuf[1]==0) break;
    pika_sleep_ms(1);
    retry++;
    if (retry >= MAX_RETRY_CHECK_AFSR) {
      pika_debug("AFSR timeout ** retry:%d", retry);
      return false;
    }
  }
  pika_debug("AFSR retry:%d", retry);
  return true;
}

void epd_update_area(spi_device_handle_t spi, int x, int y, int w, int h, int mode ) {
        //# rounded up to be multiple of 4
        //if self._direction:
            x = (x + 3) & 0xFFFC;
        //else:
        //    x = (x + 3) & 0xFFFC
        //    y = (y + 3) & 0xFFFC
  epd_check_AFSR(spi);
  //        if self._direction:
            if (x + w > EPD_WIDTH)
                w = EPD_WIDTH - x;
            if (y + h > EPD_HEIGHT)
                h = EPD_HEIGHT -y;
  //      else:
  //          if x + w > EPD_HEIGHT:
  //              w = EPD_HEIGHT - x
  //          if y + h > EPD_WIDTH:
  //              h = EPD_WIDTH -y

  uint16_t args[7];
  //      if self._rotate == IT8951_ROTATE_0:
            args[0]=x;
            args[1]=y;
            args[2]=w;
            args[3]=h;
            /*
        elif self._rotate == IT8951_ROTATE_90:
            args[0]=y
            args[1]=EPD_HEIGHT - w -x
            args[2]= h
            args[3]= w
        elif self._rotate == IT8951_ROTATE_180:
            args[0] = EPD_WIDTH - w - x;
            args[1] = EPD_HEIGHT - h - y;
            args[2] = w;
            args[3] = h;
        elif self._rotate == IT8951_ROTATE_270:
            args[0] = EPD_WIDTH - h - y;
            args[1] = x;
            args[2] = h;
            args[3] = w;
  */
  write_command(spi, IT8951_I80_CMD_DPY_AREA);
  write_word(spi, args[0]);
  write_word(spi, args[1]);
  write_word(spi, args[2]);
  write_word(spi, args[3]);
  write_word(spi, mode);

}

void epd_set_area(spi_device_handle_t spi, uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
//    def set_area(self, x, y, w, h):
  uint16_t  args0=  _endian_type << 8 |  _pix_bpp << 4 |  IT8951_ROTATE_0;
  //        args = [args0, x, y, w, h]
  write_command(spi, IT8951_TCON_LD_IMG_AREA);
  write_word(spi, args0);
  write_word(spi, x);
  write_word(spi, y);
  write_word(spi, w);
  write_word(spi, h);
}

void epd_clear(spi_device_handle_t spi) {
  _endian_type = IT8951_LDIMG_L_ENDIAN;
  _pix_bpp = IT8951_4BPP;

  epd_set_target_memory_addr(spi, 0x001236E0);
  //if _direction:
  epd_set_area(spi, 0,0, EPD_WIDTH, EPD_HEIGHT);
  //      else:
  //          self.set_area(0,0, EPD_HEIGHT, EPD_WIDTH)

  uint8_t* txbuf;
  txbuf = (uint8_t*) pikaMalloc(LIMIT);
  memset(txbuf, 0, LIMIT);

  cs_level(0);
  hw_spi_transfer(spi, LIMIT, txbuf, NULL);
  cs_level(1);
  pikaFree(txbuf, LIMIT);

  write_command(spi, IT8951_TCON_LD_IMG_END );
}

void epd_write_part_gram_4bpp(spi_device_handle_t spi, int x, int y, int w, int h){
  _endian_type = IT8951_LDIMG_B_ENDIAN;
  _pix_bpp = IT8951_4BPP;

  // note w must be multiple of 4
  x = (x +3) & 0xFFFC;
  if (x > EPD_WIDTH || y > EPD_HEIGHT) {
    pika_debug("x or y > limit");
    return;
  }
  epd_set_target_memory_addr(spi, 0x001236E0);
  epd_set_area(spi, x,y,w,h);
  int limit =(int) (w *h /2);
  uint8_t* epd_buf;
  epd_buf = g_epd_buf;
  
  if (w < EPD_WIDTH) {
    epd_buf = (uint8_t*) pikaMalloc(limit);
    int st =0;
    int et = (int) (w/2);
    int inc =(int) (EPD_WIDTH /2);
    int jnc = et;
    int j=0;
    for (int i=0; i<h; i++) {
      for (int ix=st; ix < et; ix++) {
	epd_buf[j]=g_epd_buf[ix];
      }
      j+=jnc;
      st+=inc;
      et+=inc;
    }
    pikaFree(epd_buf, limit);
  }
  cs_level(0);
  hw_spi_transfer(spi, limit, epd_buf, NULL);
  cs_level(1);
  
  write_command(spi, IT8951_TCON_LD_IMG_END );  
}

void epd_show_area(spi_device_handle_t spi, int x, int y, int w, int h, int mode){
  epd_write_part_gram_4bpp(spi,x,y,w,h);
  epd_update_area(spi, x,y,w,h,mode);
}

void epd_show(spi_device_handle_t spi) {
  epd_show_area(spi, 0,0, EPD_WIDTH, EPD_HEIGHT, UPDATE_MODE_GC16);
}

void spi_disable(spi_device_handle_t spi) {
  switch (spi_bus_remove_device(spi)) {
  case ESP_ERR_INVALID_ARG:
    pika_debug("invalid configuration");
    return;
    
  case ESP_ERR_INVALID_STATE:
    pika_debug("SPI device already free");
    return;
  }
}

void spi_enable(spi_device_handle_t spi){
  spi_device_interface_config_t devcfg={
    .clock_speed_hz=20*1000*1000,           //Clock out at 20 MHz
    .mode=0, //SPI mode 0
    .spics_io_num = -1,
    .queue_size=7,
  };
  //Initialize the SPI bus
  int dma_chan=1;

  esp_err_t ret;
  //Attach the LCD to the SPI bus
  ret=spi_bus_add_device(LCD_HOST, &devcfg, &g_t_spi);
  ESP_ERROR_CHECK(ret);
}

void EPD_Init(void) {
  pika_debug("0000 ****");
  
  pika_hal_GPIO_config cfg_in ={0};
  cfg_in.dir= PIKA_HAL_GPIO_DIR_IN;
  cfg_in.pull = PIKA_HAL_GPIO_PULL_UP;

  pika_hal_GPIO_config cfg_out={0};
  cfg_out.dir = PIKA_HAL_GPIO_DIR_OUT;
  
  pika_dev *pin_busy = pika_hal_open(PIKA_HAL_GPIO, BUSY_PIN);
  pika_hal_ioctl(pin_busy, PIKA_HAL_IOCTL_CONFIG, &cfg_in);
  pika_hal_ioctl(pin_busy, PIKA_HAL_IOCTL_ENABLE);
  
  pika_dev *pin_cs = pika_hal_open(PIKA_HAL_GPIO, CS_PIN);
  pika_hal_ioctl(pin_cs, PIKA_HAL_IOCTL_CONFIG, &cfg_out);
  pika_hal_ioctl(pin_cs, PIKA_HAL_IOCTL_ENABLE);
  _pika_hal_GPIO_write(pin_cs, 1);
  
  pika_dev *main_pwr_pin = pika_hal_open(PIKA_HAL_GPIO, MAIN_PWR_PIN);
  pika_hal_ioctl(main_pwr_pin, PIKA_HAL_IOCTL_CONFIG, &cfg_out);
  pika_hal_ioctl(main_pwr_pin, PIKA_HAL_IOCTL_ENABLE);
  
  // enable main power
  _pika_hal_GPIO_write(main_pwr_pin, 1);
  pika_debug("111111");

  //self.ext_pwr_en_pin = Pin(M5EPD_EXT_PWR_EN_PIN, Pin.OUT)
  pika_dev *ext_pwr_en_pin = pika_hal_open(PIKA_HAL_GPIO, EXT_PWR_EN_PIN);
  pika_hal_ioctl(ext_pwr_en_pin, PIKA_HAL_IOCTL_CONFIG, &cfg_out);
  pika_hal_ioctl(ext_pwr_en_pin, PIKA_HAL_IOCTL_ENABLE);
  
  //self.epd_pwr_en_pin = Pin(M5EPD_EPD_PWR_EN_PIN, Pin.OUT)
  pika_dev *epd_pwr_en_pin = pika_hal_open(PIKA_HAL_GPIO, EPD_PWR_EN_PIN);
  pika_hal_ioctl(epd_pwr_en_pin, PIKA_HAL_IOCTL_CONFIG, &cfg_out);
  pika_hal_ioctl(epd_pwr_en_pin, PIKA_HAL_IOCTL_ENABLE);
  
  //self.key_right_pin =  Pin(M5EPD_KEY_RIGHT_PIN, Pin.IN)
  pika_dev *key_right_pin = pika_hal_open(PIKA_HAL_GPIO, KEY_RIGHT_PIN);
  pika_hal_ioctl(key_right_pin, PIKA_HAL_IOCTL_CONFIG, &cfg_in);
  pika_hal_ioctl(key_right_pin, PIKA_HAL_IOCTL_ENABLE);
  
  //self.key_push_pin =  Pin(M5EPD_KEY_PUSH_PIN, Pin.IN)
  pika_dev *key_push_pin = pika_hal_open(PIKA_HAL_GPIO, KEY_PUSH_PIN);
  pika_hal_ioctl(key_push_pin, PIKA_HAL_IOCTL_CONFIG, &cfg_in);
  pika_hal_ioctl(key_push_pin, PIKA_HAL_IOCTL_ENABLE);
  
  //self.key_left_pin =  Pin(M5EPD_KEY_LEFT_PIN, Pin.IN)
  pika_dev *key_left_pin = pika_hal_open(PIKA_HAL_GPIO, KEY_LEFT_PIN);
  pika_hal_ioctl(key_left_pin, PIKA_HAL_IOCTL_CONFIG, &cfg_in);
  pika_hal_ioctl(key_left_pin, PIKA_HAL_IOCTL_ENABLE);

  pika_sleep_ms(100);

  // enableExtPower
  _pika_hal_GPIO_write(ext_pwr_en_pin, 1);
  
  // enableEpdPower
  _pika_hal_GPIO_write(epd_pwr_en_pin, 1);

  pika_sleep_ms(100);

//  g_epd_dev.sck = sck;  //fix me
//  g_epd_dev.mosi = mosi; //fix me
//  g_epd_dev.miso = miso; //fix me
  /*
  g_epd_dev.pin_busy = pin_busy;
  g_epd_dev.pin_cs = pin_cs;
  g_epd_dev.main_pwr_pin = main_pwr_pin;
  g_epd_dev.ext_pwr_en_pin = ext_pwr_en_pin;
  g_epd_dev.epd_pwr_en_pin = epd_pwr_en_pin;
  g_epd_dev.key_right_pin = key_right_pin;
  g_epd_dev.key_push_pin = key_push_pin;
  g_epd_dev.key_left_pin = key_left_pin;
  */
  // start_spi
  //pika_dev *spi = pika_hal_open(PIKA_HAL_SOFT_SPI, "spi");
  // config spi
  /*
    pika_hal_SOFT_SPI_config cfg_spi = {0};
  cfg_spi.SCK = pika_hal_open(PIKA_HAL_GPIO, SCK_PIN);
  cfg_spi.CS = pika_hal_open(PIKA_HAL_GPIO, CS_PIN);
  cfg_spi.MOSI = pika_hal_open(PIKA_HAL_GPIO, MOSI_PIN);
  cfg_spi.MISO = pika_hal_open(PIKA_HAL_GPIO, MISO_PIN);
 
  pika_hal_ioctl(spi, PIKA_HAL_IOCTL_CONFIG, &cfg_spi);
  pika_hal_ioctl(spi, PIKA_HAL_IOCTL_ENABLE);

  g_epd_dev.spi = spi;
  */

  //spi_device_handle_t spi;
  spi_bus_config_t buscfg={
    .miso_io_num=PIN_NUM_MISO,
    .mosi_io_num=PIN_NUM_MOSI,
    .sclk_io_num=PIN_NUM_CLK,
    .quadwp_io_num=-1,
    .quadhd_io_num=-1,
    //.max_transfer_sz=PARALLEL_LINES*320*2+8
  };
  
  spi_device_interface_config_t devcfg={
    .clock_speed_hz=20*1000*1000,           //Clock out at 20 MHz
    .mode=0, //SPI mode 0
    .spics_io_num = -1,
    .queue_size=7,                          
  };
  //Initialize the SPI bus
  int dma_chan=1;

  esp_err_t ret;
  ret=spi_bus_initialize(LCD_HOST, &buscfg, dma_chan);
  ESP_ERROR_CHECK(ret);
  //Attach the LCD to the SPI bus
  ret=spi_bus_add_device(LCD_HOST, &devcfg, &g_t_spi);
  ESP_ERROR_CHECK(ret);

  // init IT8951
  // get dev info
  //lcd_get_dev_info(spi);
  
  spi_device_handle_t spi = g_t_spi;
  
  write_command(spi, IT8951_TCON_SYS_RUN);
  write_reg(spi, IT8951_I80CPCR, 0x0001); //  # enable pack write

  // set vcom to -2.30v
  write_command(spi, 0x0039); //# tcon vcom set command
  write_word(spi, 0x0001);
  write_word(spi, 2300);

  epd_clear(spi);
  epd_update_area(spi, 0,0, EPD_WIDTH, EPD_HEIGHT, UPDATE_MODE_INIT );

  // simulate a framebuf operation
  g_epd_buf = pikaMalloc(LIMIT);
  frm_fill_rect(g_epd_buf, 0,0, EPD_WIDTH, EPD_HEIGHT, 0x0f);
  for (int i=0; i<100; i++) {
      frm_set_pixel(g_epd_buf, i, i, 0);
      /*
      frm_set_pixel(g_epd_buf, i+50, i, 1);
      frm_set_pixel(g_epd_buf, i+100, i, 2);
      frm_set_pixel(g_epd_buf, i+150, i, 3);
      frm_set_pixel(g_epd_buf, i+200, i, 4);
      frm_set_pixel(g_epd_buf, i+250, i, 5);
      frm_set_pixel(g_epd_buf, i+300, i, 6);
      frm_set_pixel(g_epd_buf, i+350, i, 7);
      */
  }
  frm_fill_rect(g_epd_buf, 100,100,100,100,0);
  frm_fill_rect(g_epd_buf, 200,100,100,100,2);
  frm_fill_rect(g_epd_buf, 300,100,100,100,4);
  frm_fill_rect(g_epd_buf, 400,100,100,100,6);
  frm_fill_rect(g_epd_buf, 500,100,100,100,8);    
  epd_show(spi);

  spi_disable(spi);

  pika_sleep_ms(1000);

  spi_enable(spi);
  frm_fill_rect(g_epd_buf, 500,300,100,100,10);    
  epd_show(spi);
  spi_disable(spi);

  spi_enable(spi);
  // get dev info
  lcd_get_dev_info(spi);
  spi_disable(spi);
    
  return;
}
